628 lines
19 KiB
Markdown
628 lines
19 KiB
Markdown
|
|
# 自动弹出邀请卡片实现计划
|
|||
|
|
|
|||
|
|
> **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 = [
|
|||
|
|
'<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 定义之前添加:
|
|||
|
|
|
|||
|
|
```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
|
|||
|
|
'<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` 数组中添加:
|
|||
|
|
|
|||
|
|
```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
|
|||
|
|
<!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**
|
|||
|
|
|
|||
|
|
```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 行
|