Files
ai-kefu-jc/docs/superpowers/specs/2026-06-18-dynamic-contact-config-design.md
T
2026-09-29 14:15:12 +08:00

161 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WhatsApp / Email 联系方式动态配置
**日期**: 2026-06-18
**项目**: AI 客服系统 - VOLT/HESU
**目标**: 将写死的 WhatsApp 号码和 Email 地址改为通过 `<script>` 标签的 `data-*` 属性动态配置
---
## 一、设计概述
### 1.1 项目背景
当前 AI 客服组件([static/widget.js](../../../static/widget.js))和独立聊天页([static/chat.html](../../../static/chat.html))中的 WhatsApp 跳转链接和 Email 链接是**固定写死**在源码里的:
- WhatsApp:`https://wa.me/+8619037179250`
- Email:`mailto:info@borderlesscar.com`
这些值是 `borderlesscar` 这一个部署的专属联系方式。当组件需要复用到其它品牌/站点时,必须改源码、重新部署,无法按部署点灵活配置。
### 1.2 设计目标
- 接入方在引入 `<script>` 标签时,可通过 `data-whatsapp`、`data-email` 属性传入自己的联系方式。
- **完全向后兼容**:不传任何属性时,行为与现在线上完全一致(回退到现有默认值)。
- 复用组件已有的 `data-*` 配置机制(`data-position` / `data-theme` / `data-title` / `data-auto-open` 已经走这条路径),不引入新的配置范式。
- 嵌入式组件(widget.js)和独立页(chat.html)使用**同一套**配置约定,只需学习一种机制。
### 1.3 非目标(Out of Scope)
- 后端 `.env` / [app/config.py](../../../app/config.py) / [app/services/email_service.py](../../../app/services/email_service.py) 不在本次范围内——那是服务端逻辑,与浏览器端组件配置无关。
- 后台管理面板([static/admin.html](../../../static/admin.html))里展示的联系方式是 AI 从对话中抽取的数据,不属于本次「部署级配置」范畴,不改。
- 不做按钮显隐配置(即不引入「未配置则隐藏按钮」的能力)。本次确认的策略是「未配置则回退默认值」。
---
## 二、配置约定(契约)
### 2.1 属性定义
| 属性 | 含义 | 取值格式 | 默认值(未提供时) |
|------|------|----------|--------------------|
| `data-whatsapp` | WhatsApp 联系方式跳转链接 | **完整 URL**,如 `https://wa.me/8619037179250` | `https://wa.me/+8619037179250` |
| `data-email` | Email 联系方式链接 | **完整 URL**,如 `mailto:sales@example.com` | `mailto:info@borderlesscar.com` |
### 2.2 取值格式说明
采用**完整 URL** 而非「只写号码/邮箱」:
- 组件内部不做协议拼接,直接把属性值作为 `<a href>` 使用。
- 更灵活:WhatsApp 将来可换成非 `wa.me` 的跳转地址,Email 也可带 `?subject=` 等参数。
- 接入方需自行保证带上协议前缀(`https://` / `mailto:`)。
### 2.3 缺省 / 空值处理
- 属性**缺失**:使用上表默认值。
- 属性为**空字符串**(`data-whatsapp=""`):与缺失等价,使用默认值。
- 不存在「隐藏按钮」的语义——本次范围内联系方式按钮始终展示。
### 2.4 接入示例
```html
<!-- 嵌入式组件:自定义联系方式 -->
<script
src="https://chat.example.com/static/widget.js"
data-position="bottom-right"
data-theme="light"
data-whatsapp="https://wa.me/8613800138000"
data-email="mailto:support@mybrand.com"
></script>
<!-- 独立页 chat.html:同样在它的 chat.js script 标签上传参 -->
<script
src="/static/js/chat.js"
data-whatsapp="https://wa.me/8613800138000"
data-email="mailto:support@mybrand.com"
></script>
```
---
## 三、改动详情
### 3.1 `static/widget.js`(嵌入式组件)
**读取配置**:在 [widget.js:8-12](../../../static/widget.js#L8-L12) 现有 `getAttribute` 调用旁,新增两行(沿用现有写法):
```js
var whatsapp = script.getAttribute("data-whatsapp") || "https://wa.me/+8619037179250";
var email = script.getAttribute("data-email") || "mailto:info@borderlesscar.com";
```
**使用配置**:把以下 3 处写死的 `href` 替换为字符串拼接 `+ whatsapp +` / `+ email +`:
| 位置 | 当前写死值 | 改为 |
|------|------------|------|
| [widget.js:170](../../../static/widget.js#L170)(面板 help-block,含 WhatsApp 与 Email 两个 `href`) | `https://wa.me/+8619037179250` / `mailto:info@borderlesscar.com` | `+ whatsapp +` / `+ email +` |
| [widget.js:199](../../../static/widget.js#L199)(邀请卡 WhatsApp) | `https://wa.me/+8619037179250` | `+ whatsapp +` |
| [widget.js:203](../../../static/widget.js#L203)(邀请卡 Email) | `mailto:info@borderlesscar.com` | `+ email +` |
> 注意:第 170 行是单行内同时包含 WhatsApp 与 Email 两个 `<a>`,需保证两处拼接都正确,不要漏改其中一个。
### 3.2 `static/js/chat.js` + `static/chat.html`(独立页,方案 A)
`chat.html` 是独立整页,加载 `/static/js/chat.js`(**不**加载 widget.js)。为保持与组件**同一套**配置机制,让 `chat.js` 读取自身 `<script>` 标签上的 `data-whatsapp` / `data-email` 并应用到页面两个锚点上。
**`static/js/chat.js` 改动**:
- 在 IIFE 顶部,获取自身 script 标签并读取配置(与 widget.js 同样的 `document.currentScript` 写法;为兼容异步加载,回退用 `querySelector('script[src*="chat.js"]')`):
```js
var chatScript = document.currentScript || document.querySelector('script[src*="chat.js"]');
var whatsapp = (chatScript && chatScript.getAttribute("data-whatsapp")) || "https://wa.me/+8619037179250";
var email = (chatScript && chatScript.getAttribute("data-email")) || "mailto:info@borderlesscar.com";
// 应用到页面两个锚点
var waBtn = document.querySelector('.whatsapp-btn');
var emBtn = document.querySelector('.email-btn');
if (waBtn) waBtn.href = whatsapp;
if (emBtn) emBtn.href = email;
```
**`static/chat.html` 改动**:
- 在 [chat.html:80](../../../static/chat.html#L80) 的 `<script src="/static/js/chat.js"></script>` 标签上添加 `data-whatsapp` / `data-email` 属性,使演示页展示配置能力。
- 第 35、39 行原有的静态 `href` **保留为默认值不动**,作为 `chat.js` 执行前的 fallback(避免 JS 未执行时出现空链接),`chat.js` 启动后会按配置覆盖。
- **执行时序安全**:`chat.js` 在 `<body>` 末尾加载([chat.html:80](../../../static/chat.html#L80),晚于聊天容器 DOM),故 IIFE 顶部同步 `querySelector('.whatsapp-btn' / '.email-btn')` 时两个锚点已存在,同步赋值 `href` 安全。这两个 class 仅出现在 `chat.html` 的 light DOM 中(widget.js 里同名 class 在其 shadow DOM 内,互不干扰)。
### 3.3 `static/widget-test.html`(组件测试/演示页)
- 在 [widget-test.html:69](../../../static/widget-test.html#L69) 的组件 `<script>` 标签上添加 `data-whatsapp` / `data-email` 属性,便于本地验证自定义配置生效。
---
## 四、向后兼容性
- 不传任何新属性时:widget.js 与 chat.html 的链接与当前线上**完全一致**(默认值即现有写死值)。
- 不删除任何现有 `data-*` 配置,不改变现有 DOM 结构、样式或交互。
- `chat.js` 新增逻辑仅在选中到对应元素时才设置 `href`,对无这两个按钮的页面无副作用。
---
## 五、验收 / 测试
本仓库为纯静态 JS,无自动化测试框架,采用**手动测试清单**(建议补充到 [docs/test-checklist.md](../../test-checklist.md)):
1. **默认值(不传属性)**:widget-test.html 与 chat.html 的 WhatsApp / Email 按钮分别指向 `wa.me/+8619037179250` 与 `mailto:info@borderlesscar.com`,与改动前一致。
2. **自定义值**:在 widget-test.html 组件 script 标签上传入自定义 `data-whatsapp` / `data-email`,组件内三处链接(面板 help-block、邀请卡 WhatsApp、邀请卡 Email)均指向新地址。
3. **chat.html 自定义值**:在 chat.js script 标签上传入自定义值,独立页两个按钮指向新地址。
4. **空字符串**:`data-whatsapp=""` 等价于未传,回退默认值。
5. **禁用 JS**:chat.html 在 JS 未执行时,静态 `href` 仍为默认值(不出现空链接)。
---
## 六、影响文件清单
| 文件 | 改动类型 |
|------|----------|
| `static/widget.js` | 读取 2 个新属性 + 替换 3 处 href |
| `static/js/chat.js` | 新增读取自身 script 标签配置 + 设置 2 个锚点 href |
| `static/chat.html` | chat.js script 标签加 2 个 data 属性(静态 href 保留为默认) |
| `static/widget-test.html` | 组件 script 标签加 2 个 data 属性(演示用) |