# 自动弹出邀请卡片实现计划 > **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 行),在其后添加: ```javascript // 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: 添加存储检查函数** 在同一区域添加存储管理函数: ```javascript 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** ```bash 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 定义之前添加: ```javascript // Invite card HTML template var inviteTemplate = [ '
', '
', '
', '
🤖
', '', '
', '
', '
Get stock list or quotation now
', '
Chat with us for available vehicles, delivery timing and distributor cooperation.
', '
', '', '
', '
', '
', '', '', '
', '
', '', '', '
', '
', '
' ].join(""); ``` - [ ] **Step 2: 创建邀请卡片 DOM 元素** 在 toggleBtn 定义之前添加: ```javascript var inviteEl = document.createElement("div"); inviteEl.className = "invite-container"; inviteEl.innerHTML = inviteTemplate; inviteEl.style.display = "none"; // Hidden by default ``` - [ ] **Step 3: 将邀请卡片添加到容器** 找到 `container.appendChild(toggleBtn);` 这一行,在其之前添加: ```javascript container.appendChild(inviteEl); ``` - [ ] **Step 4: 获取邀请卡片元素引用** 在现有元素引用定义区域(约第 161-165 行)添加: ```javascript 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** ```bash 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 行),在现有样式后、闭合标签前添加: ```javascript // 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` 定位: ```css ".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** ```bash git add static/widget.js git commit -m "feat: add invite card CSS styles" ``` --- ## 任务 4: 添加定时器逻辑 **文件:** - 修改: `static/widget.js` - [ ] **Step 1: 添加启动邀请定时器函数** 在存储管理函数后添加: ```javascript 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 行),在函数结束前添加: ```javascript // Start invite timer startInviteTimer(); ``` - [ ] **Step 3: 添加定时器清理** 在 `toggle()` 函数中,当打开聊天面板时取消定时器: 找到 `toggle()` 函数定义(约第 169-173 行),修改为: ```javascript 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** ```bash git add static/widget.js git commit -m "feat: add invite timer logic (1.8s delay)" ``` --- ## 任务 5: 添加邀请卡片事件处理器 **文件:** - 修改: `static/widget.js` - [ ] **Step 1: 添加关闭按钮事件** 在现有事件监听器区域(约第 175-177 行之后)添加: ```javascript // Invite close button inviteCloseBtn.addEventListener("click", function() { hideInvite("temporary"); }); ``` - [ ] **Step 2: 添加 Start Chat 按钮事件** ```javascript // Invite Start Chat button inviteStartBtn.addEventListener("click", function() { hideInvite("temporary"); if (!isOpen) { toggle(); } }); ``` - [ ] **Step 3: 添加 No Thanks 按钮事件** ```javascript // Invite No Thanks button inviteDismissBtn.addEventListener("click", function() { hideInvite("permanent"); }); ``` - [ ] **Step 4: 添加话题按钮事件** ```javascript // 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** ```bash 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 行),修改为: ```javascript '
', '
How can we help?
Ask about vehicles, market support, quotation, shipping and delivery.
📞Call us
', '', '
Hey! What kind of car are you looking for?
', '
', ``` - [ ] **Step 2: 添加热门话题 CSS 样式** 在 `style.textContent` 数组中添加: ```css // 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()` 函数中添加热门话题按钮的事件监听: ```javascript // 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()` 函数之前添加: ```javascript function autoSendMessage(text) { if (!text || !ws || ws.readyState !== WebSocket.OPEN) return; addMsg("user", text); showTyping(); ws.send(JSON.stringify({ content: text })); } ``` - [ ] **Step 5: 更新话题按钮使用新函数** 修改任务 5 中话题按钮的事件处理,使用新的辅助函数: ```javascript // 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** ```bash 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`,打开它;否则创建一个简单的测试页面: ```html Widget Invite Test

Widget Invite Card Test

Invite should appear after 1.8 seconds

``` - [ ] **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** ```bash # 如果创建了测试文件 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** ```bash 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 行