Files
ai-kefu-jc/docs/superpowers/plans/2026-04-23-auto-popup-invite-card.md
T
2026-09-29 14:15:12 +08:00

19 KiB
Raw Blame History

自动弹出邀请卡片实现计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

目标: 为 Widget 模式添加自动弹出邀请卡片,页面加载 1.8 秒后显示,提供快速访问热门话题的入口

架构: 在现有 widget.js 中扩展,使用 Shadow DOM 隔离样式,sessionStorage 管理每页独立的关闭状态

技术栈: 原生 JavaScript、Shadow DOM、sessionStorage


文件结构

修改文件:

  • static/widget.js - 添加邀请卡片组件和逻辑

新增代码段:

  1. 邀请卡片 HTML 模板
  2. 邀请卡片 CSS 样式
  3. 定时器管理
  4. 存储管理
  5. 事件处理器
  6. 自动发送消息函数
  7. 热门话题区域

任务 1: 添加邀请卡片存储管理

文件:

  • 修改: static/widget.js

在现有变量声明区域(第 153-160 行附近)添加存储管理相关变量和函数:

  • Step 1: 添加存储管理变量

在 widget.js 中找到 var wSessionId = null; 这一行(约第 155 行),在其后添加:

// Invite card state management
var inviteTimer = null;
var invitePanel = null;
var inviteShown = false;
var inviteStorageKey = "widget_invite_dismissed_" + window.location.pathname;
var isPermanentlyClosed = false;
  • Step 2: 添加存储检查函数

在同一区域添加存储管理函数:

function checkInviteStorage() {
    var dismissed = sessionStorage.getItem(inviteStorageKey);
    if (dismissed === "permanent") {
        isPermanentlyClosed = true;
        return true;
    }
    return false;
}

function setInviteDismissed(type) {
    if (type === "permanent") {
        sessionStorage.setItem(inviteStorageKey, "permanent");
        isPermanentlyClosed = true;
    } else {
        sessionStorage.setItem(inviteStorageKey, "temporary");
    }
}

function clearInviteDismissed() {
    sessionStorage.removeItem(inviteStorageKey);
    isPermanentlyClosed = false;
}
  • Step 3: Commit
git add static/widget.js
git commit -m "feat: add invite card storage management functions"

任务 2: 添加邀请卡片 HTML 模板

文件:

  • 修改: static/widget.js

  • Step 1: 添加邀请卡片 HTML 模板

在 widget.js 中找到聊天面板的 HTML 定义(约第 115-134 行,panel.innerHTML = [...]),在其后、toggleBtn 定义之前添加:

// Invite card HTML template
var inviteTemplate = [
    '<div class="invite-panel">',
    '<div class="invite-header">',
    '<div class="invite-icon-wrapper">',
    '<div class="invite-icon">🤖</div>',
    '<span class="invite-status-dot"></span>',
    '</div>',
    '<div class="invite-header-content">',
    '<div class="invite-title">Get stock list or quotation now</div>',
    '<div class="invite-subtitle">Chat with us for available vehicles, delivery timing and distributor cooperation.</div>',
    '</div>',
    '<button class="invite-close-btn" aria-label="Close">',
    '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>',
    '</button>',
    '</div>',
    '<div class="invite-body">',
    '<div class="invite-topics">',
    '<button class="invite-topic-btn" data-topic="Get Quotation">Get Quotation</button>',
    '<button class="invite-topic-btn" data-topic="Distributor Cooperation">Distributor Cooperation</button>',
    '</div>',
    '<div class="invite-actions">',
    '<button class="invite-start-btn">Start Chat',
    '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>',
    '</button>',
    '<button class="invite-dismiss-btn">No Thanks</button>',
    '</div>',
    '</div>',
    '</div>'
].join("");
  • Step 2: 创建邀请卡片 DOM 元素

在 toggleBtn 定义之前添加:

var inviteEl = document.createElement("div");
inviteEl.className = "invite-container";
inviteEl.innerHTML = inviteTemplate;
inviteEl.style.display = "none"; // Hidden by default
  • Step 3: 将邀请卡片添加到容器

找到 container.appendChild(toggleBtn); 这一行,在其之前添加:

container.appendChild(inviteEl);
  • Step 4: 获取邀请卡片元素引用

在现有元素引用定义区域(约第 161-165 行)添加:

var inviteCloseBtn = shadow.querySelector(".invite-close-btn");
var inviteStartBtn = shadow.querySelector(".invite-start-btn");
var inviteDismissBtn = shadow.querySelector(".invite-dismiss-btn");
var inviteTopicBtns = shadow.querySelectorAll(".invite-topic-btn");
  • Step 5: Commit
git add static/widget.js
git commit -m "feat: add invite card HTML template and DOM structure"

任务 3: 添加邀请卡片 CSS 样式

文件:

  • 修改: static/widget.js

  • Step 1: 添加邀请卡片基础样式

在 style.textContent 数组中(约第 38-106 行),在现有样式后、闭合标签前添加:

// Invite card container
".invite-container { position:fixed; z-index:99998; }",

".invite-panel { width:360px; max-width:calc(100vw - 24px); overflow:hidden; border-radius:26px; border:1px solid #F1CCAE; background:white; box-shadow:0 28px 80px rgba(196,91,25,0.22); opacity:0; transform:translateY(20px); transition:opacity 0.3s, transform 0.3s; pointer-events:none; }",
".invite-panel.show { opacity:1; transform:translateY(0); pointer-events:auto; }",
".invite-panel.hide { display:none; }",

// Invite header
".invite-header { background:linear-gradient(135deg, #F08A34 0%, #EC8C2F 100%); padding:20px; color:white; display:flex; align-items:flex-start; gap:12px; }",
".invite-icon-wrapper { position:relative; flex-shrink:0; }",
".invite-icon { width:44px; height:44px; background:rgba(255,255,255,0.18); border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:20px; backdrop-filter:blur(10px); }",
".invite-status-dot { position:absolute; bottom:-2px; right:-2px; width:14px; height:14px; background:#22c55e; border-radius:50%; border:3px solid #F08A34; }",
".invite-header-content { flex:1; min-width:0; }",
".invite-title { font-size:16px; font-weight:600; line-height:1.4; }",
".invite-subtitle { font-size:13px; margin-top:4px; opacity:0.9; line-height:1.5; }",
".invite-close-btn { flex-shrink:0; width:32px; height:32px; border-radius:8px; background:rgba(255,255,255,0.1); border:none; color:white; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:background 0.2s; }",
".invite-close-btn:hover { background:rgba(255,255,255,0.2); }",

// Invite body
".invite-body { padding:16px 20px; background:#FFF9F4; }",

// Topic buttons
".invite-topics { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-bottom:16px; }",
".invite-topic-btn { border-radius:22px; border:1px solid #EECFB6; background:white; padding:14px 16px; text-align:center; font-size:13px; font-weight:500; color:#8C4D1D; cursor:pointer; transition:background 0.2s; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }",
".invite-topic-btn:hover { background:#FFF7F1; }",

// Action buttons
".invite-actions { display:flex; gap:10px; }",
".invite-start-btn { flex:1; border-radius:20px; border:none; background:linear-gradient(135deg, #F08A34 0%, #EC8C2F 100%); padding:14px 20px; color:white; font-size:14px; font-weight:600; cursor:pointer; display:flex; align-items:center; justify-content:center; gap:8px; box-shadow:0 2px 8px rgba(240,138,49,0.25); transition:opacity 0.2s; }",
".invite-start-btn:hover { opacity:0.95; }",
".invite-dismiss-btn { border-radius:20px; border:1px solid #E8C4A9; background:white; padding:14px 20px; color:#A8591F; font-size:14px; font-weight:500; cursor:pointer; transition:background 0.2s; }",
".invite-dismiss-btn:hover { background:#FEFEFE; }",

// Responsive
"@media(max-width:450px){ .invite-panel{width:calc(100vw - 24px);} .invite-topics{grid-template-columns:1fr;} }",

确保在数组最后添加逗号(除了最后一项)。

  • Step 2: 调整现有聊天面板和悬浮按钮样式

找到 .chat-panel 样式定义,修改其 bottom 定位:

".chat-panel { position:absolute; bottom:80px; right:0; width:400px; height:520px; background:" + c.bg + "; border-radius:16px; box-shadow:0 10px 40px rgba(0,0,0,0.15); display:none; flex-direction:column; overflow:hidden; }",

修改 bottom:70px 为 bottom:80px,为邀请卡片留出空间。

  • Step 3: Commit
git add static/widget.js
git commit -m "feat: add invite card CSS styles"

任务 4: 添加定时器逻辑

文件:

  • 修改: static/widget.js

  • Step 1: 添加启动邀请定时器函数

在存储管理函数后添加:

function startInviteTimer() {
    if (checkInviteStorage()) {
        return; // Already dismissed, don't show
    }

    inviteTimer = window.setTimeout(function() {
        if (!isOpen) {
            showInvite();
        }
    }, 1800); // 1.8 seconds
}

function showInvite() {
    if (inviteShown || isPermanentlyClosed) return;
    inviteShown = true;
    invitePanel = shadow.querySelector(".invite-panel");
    if (invitePanel) {
        invitePanel.classList.remove("hide");
        invitePanel.classList.add("show");
    }
}

function hideInvite(type) {
    if (!invitePanel) return;
    invitePanel.classList.remove("show");
    invitePanel.classList.add("hide");
    setInviteDismissed(type || "temporary");
}
  • Step 2: 在 init 函数末尾启动定时器

找到 init() 函数的末尾(约第 265 行),在函数结束前添加:

// Start invite timer
startInviteTimer();
  • Step 3: 添加定时器清理

在 toggle() 函数中,当打开聊天面板时取消定时器:

找到 toggle() 函数定义(约第 169-173 行),修改为:

function toggle() {
    isOpen = !isOpen;
    panel.classList.toggle("open", isOpen);

    if (isOpen) {
        if (inviteTimer) {
            window.clearTimeout(inviteTimer);
            inviteTimer = null;
        }
        hideInvite("temporary");
        if (!wWs) wConnect();
    } else {
        // Only restart timer if not permanently closed
        if (!isPermanentlyClosed && !checkInviteStorage()) {
            inviteShown = false;
            startInviteTimer();
        }
    }
}
  • Step 4: Commit
git add static/widget.js
git commit -m "feat: add invite timer logic (1.8s delay)"

任务 5: 添加邀请卡片事件处理器

文件:

  • 修改: static/widget.js

  • Step 1: 添加关闭按钮事件

在现有事件监听器区域(约第 175-177 行之后)添加:

// Invite close button
inviteCloseBtn.addEventListener("click", function() {
    hideInvite("temporary");
});
  • Step 2: 添加 Start Chat 按钮事件
// Invite Start Chat button
inviteStartBtn.addEventListener("click", function() {
    hideInvite("temporary");
    if (!isOpen) {
        toggle();
    }
});
  • Step 3: 添加 No Thanks 按钮事件
// Invite No Thanks button
inviteDismissBtn.addEventListener("click", function() {
    hideInvite("permanent");
});
  • Step 4: 添加话题按钮事件
// Invite topic buttons - auto send message
Array.prototype.forEach.call(inviteTopicBtns, function(btn) {
    btn.addEventListener("click", function() {
        var topic = this.getAttribute("data-topic");
        hideInvite("temporary");

        if (!isOpen) {
            toggle();
        }

        // Auto send message after chat opens
        setTimeout(function() {
            if (ws && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({ content: topic }));
                addMsg("user", topic);
                inputEl.value = "";
                showTyping();
            }
        }, 300);
    });
});
  • Step 5: Commit
git add static/widget.js
git commit -m "feat: add invite card event handlers"

任务 6: 添加聊天面板热门话题区域

文件:

  • 修改: static/widget.js

  • Step 1: 修改消息区域 HTML

找到 panel.innerHTML 定义中的消息区域部分(约第 124-127 行),修改为:

'<div class="panel-messages" id="w-msgs">',
'<div class="help-block"><div style="display:flex;align-items:center;gap:14px;"><div class="help-content"><div class="help-title">How can we help?</div><div class="help-description">Ask about vehicles, market support, quotation, shipping and delivery.</div></div><a href="https://wa.me/+8619037179250" target="_blank" class="call-btn"><span style="font-size:15px;">📞</span><span>Call us</span></a></div></div>',
'<div class="popular-topics" id="w-popular-topics"><div class="popular-topics-label">POPULAR TOPICS</div><div class="popular-topics-grid"><button class="popular-topic-btn" data-topic="Get Quotation">Get Quotation</button><button class="popular-topic-btn" data-topic="Distributor Cooperation">Distributor Cooperation</button></div></div>',
'<div class="msg-wrapper"><div class="msg ai">Hey! What kind of car are you looking for?</div></div>',
'</div>',
  • Step 2: 添加热门话题 CSS 样式

在 style.textContent 数组中添加:

// Popular Topics
".popular-topics { margin-bottom:16px; }",
".popular-topics-label { font-size:11px; font-weight:600; color:#C16C2E; letter-spacing:0.14em; margin-bottom:8px; text-transform:uppercase; }",
".popular-topics-grid { display:flex; gap:8px; flex-wrap:wrap; }",
".popular-topic-btn { border-radius:20px; border:1px solid #F0D5BF; background:#FFF8F3; padding:8px 16px; font-size:12px; font-weight:500; color:#8C4D1D; cursor:pointer; transition:background 0.2s; white-space:nowrap; }",
".popular-topic-btn:hover { background:white; }",
  • Step 3: 添加热门话题事件处理

在 init() 函数中添加热门话题按钮的事件监听:

// Popular topics buttons
var popularTopicBtns = shadow.querySelectorAll(".popular-topic-btn");
Array.prototype.forEach.call(popularTopicBtns, function(btn) {
    btn.addEventListener("click", function() {
        var topic = this.getAttribute("data-topic");

        // Hide popular topics after first interaction
        var popularTopicsEl = shadow.getElementById("w-popular-topics");
        if (popularTopicsEl) {
            popularTopicsEl.style.display = "none";
        }

        // Send message
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ content: topic }));
            addMsg("user", topic);
            inputEl.value = "";
            showTyping();
        }
    });
});
  • Step 4: 添加自动发送消息辅助函数

在 wSend() 函数之前添加:

function autoSendMessage(text) {
    if (!text || !ws || ws.readyState !== WebSocket.OPEN) return;
    addMsg("user", text);
    showTyping();
    ws.send(JSON.stringify({ content: text }));
}
  • Step 5: 更新话题按钮使用新函数

修改任务 5 中话题按钮的事件处理,使用新的辅助函数:

// Invite topic buttons - auto send message
Array.prototype.forEach.call(inviteTopicBtns, function(btn) {
    btn.addEventListener("click", function() {
        var topic = this.getAttribute("data-topic");
        hideInvite("temporary");

        if (!isOpen) {
            toggle();
        }

        // Auto send message after chat opens
        setTimeout(function() {
            autoSendMessage(topic);
        }, 300);
    });
});
  • Step 6: Commit
git add static/widget.js
git commit -m "feat: add popular topics section to chat panel"

任务 7: 测试和验证

文件:

  • 测试: static/widget-test.html 或在浏览器中测试

  • Step 1: 创建测试页面

如果有 widget-test.html,打开它;否则创建一个简单的测试页面:

<!DOCTYPE html>
<html>
<head>
    <title>Widget Invite Test</title>
</head>
<body>
    <h1>Widget Invite Card Test</h1>
    <p>Invite should appear after 1.8 seconds</p>
    <script src="/static/widget.js" data-position="bottom-right" data-theme="light"></script>
</body>
</html>
  • Step 2: 测试基本功能

打开测试页面,验证:

  1. ✅ 页面加载 1.8 秒后邀请卡片自动弹出
  2. ✅ 邀请卡片样式正确(橙色头部、两个话题按钮)
  3. ✅ 点击 "Get Quotation" → 邀请关闭 → 聊天打开 → 消息自动发送
  4. ✅ 点击 "Distributor Cooperation" → 邀请关闭 → 聊天打开 → 消息自动发送
  5. ✅ 点击 "Start Chat" → 邀请关闭 → 聊天打开
  6. ✅ 点击 "No Thanks" → 邀请永久关闭
  7. ✅ 点击悬浮按钮(邀请可见时)→ 邀请关闭 → 聊天打开
  8. ✅ 点击悬浮按钮(邀请隐藏时)→ 聊天打开
  • Step 3: 测试页面独立性

在多个标签页/页面中测试:

  1. ✅ 页面 A 关闭邀请后,页面 B 仍能弹出邀请
  2. ✅ 页面 A 刷新后,已关闭状态保持
  3. ✅ 关闭整个网站重新打开,所有状态重置
  • Step 4: 测试热门话题
  1. ✅ 聊天面板显示 "POPULAR TOPICS" 区域
  2. ✅ 点击话题按钮 → 消息自动发送
  3. ✅ 发送第一条消息后,热门话题隐藏
  • Step 5: 测试响应式

在不同屏幕尺寸下测试:

  1. ✅ 移动端(<450px)样式正确
  2. ✅ 话题按钮在小屏幕上正确排列
  • Step 6: 测试边界情况
  1. ✅ 快速连续点击悬浮按钮
  2. ✅ 在邀请弹出前打开聊天
  3. ✅ 在消息发送过程中关闭邀请
  • Step 7: Commit
# 如果创建了测试文件
git add static/widget-test.html
git commit -m "test: add widget invite card test page"

任务 8: 最终清理和文档

  • Step 1: 代码审查

检查代码:

  1. ✅ 无 console.log 调试语句
  2. ✅ 变量命名一致
  3. ✅ 注释清晰
  4. ✅ 无重复代码
  • Step 2: 浏览器兼容性测试

在不同浏览器中测试:

  1. ✅ Chrome/Edge (Chromium)
  2. ✅ Firefox
  3. ✅ Safari (如果可用)
  • Step 3: 性能检查
  1. ✅ 无内存泄漏(定时器清理)
  2. ✅ 无不必要的 DOM 操作
  3. ✅ 存储操作高效
  • Step 4: 最终 Commit
git add static/widget.js
git commit -m "chore: final cleanup and polish invite card feature"

验收标准

功能要求

  • ✅ 页面加载 1.8 秒后自动弹出邀请卡片
  • ✅ 包含两个话题按钮:Get Quotation、Distributor Cooperation
  • ✅ 点击话题按钮 → 关闭邀请 → 打开聊天 → 自动发送消息
  • ✅ 点击 "Start Chat" → 关闭邀请 → 打开聊天
  • ✅ 点击 "No Thanks" → 永久关闭邀请
  • ✅ 每个页面独立管理状态
  • ✅ 聊天面板显示热门话题区域

样式要求

  • ✅ 橙色渐变头部:linear-gradient(135deg, #F08A34 0%, #EC8C2F 100%)
  • ✅ 圆角 26px
  • ✅ 阴影效果正确
  • ✅ 淡入 + 滑入动画
  • ✅ 响应式设计

技术要求

  • ✅ 使用 sessionStorage 存储状态
  • ✅ 使用 Shadow DOM 隔离样式
  • ✅ 定时器正确清理
  • ✅ 无内存泄漏

预计工作量: 约 2-3 小时 文件修改: 1 个文件(static/widget.js) 新增代码: 约 250-300 行