Co-Authored-By: Claude Code <noreply@anthropic.com>
12 KiB
邀请卡自动弹出开关 + 移动端悬浮按钮配置
日期: 2026-06-23
项目: AI 客服系统 - VOLT/HESU
目标: 为嵌入式组件(widget.js)新增两个 data-* 配置开关——控制「邀请卡是否自动弹出」、以及「是否使用移动端悬浮按钮样式」,并让移动端按钮采用「图标在上、文字在下」的竖排样式(D2)。
一、设计概述
1.1 项目背景
当前嵌入式组件 static/widget.js 有两处行为是写死、无法按部署点配置的:
- 邀请卡固定 1.8s 后自动弹出:widget.js:306-316 的
startInviteTimer()无条件启动,所有嵌入站点都会弹。部分站点不希望主动打扰用户,但目前无法关闭。 - 悬浮按钮只有桌面款一种样式:widget.js:233-244 的
toggle-btn是一个「图标在左、文字在右」的宽药丸。在移动端窄屏上偏宽、略挡内容。
本次复用上一轮「联系方式动态配置」已建立的 data-* 配置范式(见 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),各管各的,本次不统一。- 不做自动检测:移动端判定采用纯手动标识(方案 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 的 data-auto-open 一致):
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 接入示例
<!-- 桌面端、保留邀请卡自动弹(什么都不加,现状) -->
<script src="https://chat.example.com/static/widget.js"></script>
<!-- 移动端站点:竖排按钮 + 关闭邀请卡自动弹 -->
<script
src="https://chat.example.com/static/widget.js"
data-mobile="true"
data-auto-invite="false"
></script>
<!-- 仅关闭邀请卡自动弹,按钮仍为桌面款 -->
<script
src="https://chat.example.com/static/widget.js"
data-auto-invite="false"
></script>
两个开关互相独立,可任意组合。
三、改动详情
全部改动集中在 static/widget.js 与测试页 static/widget-test.html。
3.1 static/widget.js — 读取两个新属性
在 widget.js:8-14 现有 getAttribute 调用旁,新增两行:
var autoInvite = script.getAttribute("data-auto-invite") !== "false";
var isMobile = script.getAttribute("data-mobile") === "true";
3.2 static/widget.js — 邀请卡自动弹出开关
在 widget.js:306 的 startInviteTimer() 函数最开头加一句短路:
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 一处生成,toggleBtn.innerHTML 为桌面款模板。改动为按 isMobile 分两套模板,点击事件不变。
实现路径选择(见第四节):采用「独立移动端 DOM 模板」,桌面款模板逐字不动搬入 else 分支。
DOM 结构(D2,竖排:圆钮在上、文字在外在下):
var toggleBtn = document.createElement("button");
toggleBtn.className = "toggle-btn" + (isMobile ? " mobile" : "");
if (isMobile) {
toggleBtn.innerHTML = `
<div class="mobile-circle">🤖<span class="mobile-dot"></span></div>
<span class="mobile-label">Chat with us</span>
`;
} else {
toggleBtn.innerHTML = `/* 现有桌面款模板(widget.js:235-244),原样照搬、逐字不动 */`;
}
toggleBtn 仍是接收点击的元素(widget.js:367 的 toggleBtn.addEventListener("click", toggle) 不变),点击圆钮或文字均触发 toggle()。
新增 CSS(追加到 widget.js:40 注入的样式数组中):
// 移动端竖排按钮(D2):覆盖桌面款药丸背景,把橙色挪到圆钮上
".toggle-btn.mobile { flex-direction:column; gap:5px; padding:0; background:transparent; box-shadow:none; border-radius:0; }",
".toggle-btn.mobile:hover { transform:scale(1.05); }",
".toggle-btn.mobile .mobile-circle { position:relative; width:56px; height:56px; border-radius:50%; background:#f08a31; display:flex; align-items:center; justify-content:center; font-size:28px; box-shadow:0 4px 16px rgba(240,138,49,0.45); }",
".toggle-btn.mobile .mobile-dot { position:absolute; bottom:2px; right:2px; width:13px; height:13px; background:#22c55e; border-radius:50%; border:2px solid #f08a31; }",
".toggle-btn.mobile .mobile-label { color:#8C4D1D; font-size:10px; font-weight:600; line-height:1.1; }",
位置:桌面端位置逻辑不变——按钮仍位于由
positionStyles决定的容器锚点(默认右下)。移动端采用更贴角的位置表(见下「位置偏移」)。颜色与主题:移动端按钮颜色硬编码固定橙色(
#f08a31圆钮 +#8C4D1D文字),不随data-theme切换——这与现有桌面款按钮(同样固定橙色、忽略data-theme)行为一致,实现时无需特殊处理主题。
位置偏移(移动端贴角):
现有 positionStyles(widget.js:16-21)的 bottom-right 取 bottom:80px(为高药丸按钮预留)。移动端圆钮仅 56px,沿用 80px 会在手机上离底边偏高、显得"飘"。故 isMobile 时改用一份更贴角的位置表 positionStylesMobile:
var positionStylesMobile = {
"bottom-right": { bottom: "20px", right: "16px" },
"bottom-left": { bottom: "20px", left: "16px" },
"top-right": { top: "20px", right: "16px" },
"top-left": { top: "20px", left: "16px" },
};
var pos = (isMobile ? positionStylesMobile : positionStyles)[position]
|| (isMobile ? positionStylesMobile : positionStyles)["bottom-right"];
桌面端 positionStyles 逐字不动;仅当 data-mobile="true" 时切换到 positionStylesMobile。聊天面板/邀请卡因相对容器定位(bottom:80px),随容器下移后仍开在按钮上方,不重叠。
3.4 static/widget-test.html — 演示 / 测试属性
在 widget-test.html:69 的组件 <script> 标签上,新增 data-mobile / data-auto-invite 属性以便本地验证;或保留默认、另起一个测试用例覆盖移动端。具体由实现阶段在测试清单中明确。
四、实现路径:独立移动端 DOM 模板 vs CSS 类切换
移动端按钮(D2)与桌面款 DOM 结构差异较大:桌面是「图标+文字都在一个橙色药丸里」,D2 是「圆钮在上、文字在按钮外面」。两种实现法:
| 方案 | 做法 | 取舍 |
|---|---|---|
| ① 独立移动端 DOM 模板(采用) | isMobile 时拼一段不同的 innerHTML,桌面模板原样搬进 else |
结构清晰、互不干扰;改一边不影响另一边;与「桌面分支逐字不变」的兼容性目标天然吻合 |
| ② 同一 DOM + CSS 类切换 | 保留一份结构,加 .mobile class 用 CSS 把文字挪到按钮外 |
代码略少,但要硬掰布局、容易出现重叠/错位,调试成本高 |
采用 ①:D2 结构差异够大,独立模板最干净,且桌面分支零改动 = 现有嵌入零风险。
五、向后兼容性
- 不传任何新属性:
autoInvite=true、isMobile=false。- 邀请卡仍 1.8s 自动弹(
startInviteTimer正常执行)。 - 悬浮按钮仍走桌面款模板(
toggle-btn,无.mobileclass,样式与现状完全一致)。 - → 现有所有嵌入脚本(均未写这两个新属性)行为与线上完全一致。
- 邀请卡仍 1.8s 自动弹(
- 不删除、不修改任何现有
data-*配置(data-position/data-theme/data-title/data-auto-open/data-whatsapp/data-email)。 - 唯一风险点:悬浮按钮那段是所有嵌入共享的代码路径。规避方式 = 桌面分支逐字不动、移动 D2 完全独立成分支(见第四节)。
- 邀请卡改动仅为函数开头加一句
if (!autoInvite) return;,极小、极孤立。
六、验收 / 测试
本仓库为纯静态 JS,无自动化测试框架,采用手动测试清单(建议补充到 docs/test-checklist.md):
- 默认值回归(不传新属性):用 widget-test.html 验证——邀请卡仍 1.8s 后弹出;悬浮按钮仍为桌面款宽药丸;点击按钮正常开关聊天面板。与改动前逐项一致。
data-auto-invite="false":邀请卡不再自动弹出;悬浮按钮(桌面款)点击仍能打开聊天面板;聊天面板内交互正常。data-mobile="true":悬浮按钮变为 D2 竖排样式(56px 圆钮在上、"Chat with us" 文字外置在下、非橙色底);点击圆钮或文字均能开关聊天面板;邀请卡仍按默认自动弹(两者独立)。- 组合
data-mobile="true" data-auto-invite="false":竖排按钮 + 不弹邀请卡,两者互不影响。 - 空值/非法值:
data-mobile=""或data-mobile="yes"等价于未传(回退桌面款);data-auto-invite="0"等价于未传(仍弹)。 - 位置配置:
data-mobile="true"配合data-position各取值,按钮锚点位置正确;尤其验证bottom-right下移动端按钮贴角(约bottom:20px)而非沿用桌面80px。
七、影响文件清单
| 文件 | 改动类型 |
|---|---|
static/widget.js |
读取 2 个新属性 + 邀请卡短路 1 行 + 悬浮按钮分两套模板 + 新增移动端 CSS |
static/widget-test.html |
组件 script 标签加 data-mobile / data-auto-invite(演示/测试用) |