Appearance
React 与 Vue 接入示例
合从不提供框架适配包,React、Vue 项目用的是同一段 script 标签,在组件里调 window.hecong(cb) 即可。
script 标签放在 index.html
不要在组件里动态插入 script。聊天窗是页面级的,跟着页面走,不随组件的创建销毁。Nuxt 这类没有静态入口 HTML 的框架是例外,在应用级入口动态插入一次,写法见接入代码与配置项。
html
<!-- index.html -->
<body>
<div id="root"></div>
<script async
src="https://assets.aihecong.com/sdk/hecong.js"
data-channel-id="0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b">
</script>
<script>
window.hecong = window.hecong || function (c) { (window.hecong.q = window.hecong.q || []).push(c) }
</script>
</body>第二段里那行占位函数照原样复制,不要改写:组件里的调用可能跑在接入脚本加载完成之前,有了它 window.hecong 从一开始就存在,早调的回调排队等脚本就绪。详见接入代码与配置项。
Next.js、Nuxt 这类框架的写法见接入代码与配置项。
传客户身份
window.hecong(cb) 早调晚调都能拿到命令对象,所以你只要在业务上知道客户是谁的那一刻调用即可。
jsx
import { useEffect } from 'react'
function useHecongIdentity(user) {
useEffect(() => {
if (!user) return
window.hecong((hc) => {
hc.identify({ id: user.id, profile: { name: user.name } })
})
}, [user])
}
// 客户退出登录时
function onLogout() {
window.hecong((hc) => hc.reset())
}js
import { watchEffect } from 'vue'
watchEffect(() => {
if (!user.value) return
window.hecong((hc) => {
hc.identify({ id: user.value.id, profile: { name: user.value.name } })
})
})
// 客户退出登录时
function onLogout() {
window.hecong((hc) => hc.reset())
}哪些要清理,哪些不用
聊天窗本身不需要任何清理,组件卸载了它照样在页面上。
但你注册进去的东西要收回,否则组件重新挂载时会重复注册:
| 你调了什么 | 卸载时要做什么 |
|---|---|
hc.identify() | 什么都不用做 |
hc.on(...) | 调它返回的退订函数 |
hc.registerComposerAction(...) / hc.registerQuickReply(...) | 调它返回的注销函数 |
jsx
useEffect(() => {
let off
let cancelled = false
window.hecong((hc) => {
if (cancelled) return
off = hc.on('message:incoming', handleIncoming)
})
return () => {
cancelled = true
off?.()
}
}, [])组件可能在聊天窗脚本就绪前就卸载 —— React 开发模式下 StrictMode 会挂载、卸载、再挂载一次,必然踩到。这时 off 还没拿到,cancelled 的作用就是让迟到的回调不再注册。
hc.on() 返回的退订函数在任何阶段调用都有效,聊天窗还没加载时它会把这次订阅从待执行队列里撤掉。
另外,window.hecong(cb) 的返回值不是退订函数,不要这么用。
Vue 里对应写在 onBeforeUnmount 中。
用自己的按钮打开聊天窗
jsx
function AskButton() {
return (
<button onClick={() => window.hecong((hc) => hc.open())}>
咨询客服
</button>
)
}TypeScript 项目
合从没有发布类型声明包,自己声明一下全局即可:
ts
// hecong.d.ts
type HecongProfile = { name?: string; phone?: string; email?: string; avatar?: string }
type HecongData = Record<string, string | number | boolean>
type HecongPickerType = 'product' | 'order' | 'article'
type HecongAction = { id: string; label: string; icon?: string; onClick(): void }
interface HecongApi {
identify(args: { id: string; profile?: HecongProfile; data?: HecongData }): Promise<unknown>
reset(): Promise<unknown>
open(): void
close(): void
toggle(): void
setLocale(locale: string): void
setColorScheme(scheme: 'light' | 'dark' | 'auto'): void
registerComposerAction(action: HecongAction): () => void
registerQuickReply(action: HecongAction): () => void
setPickerData(type: HecongPickerType, items: unknown[]): void
openPicker(type: HecongPickerType): void
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(event: string, handler: (arg: any) => void): () => void
readonly isOpen: boolean
readonly state: 'idle' | 'connecting' | 'open' | 'reconnecting' | 'closed'
}
declare global {
interface Window {
hecong: (cb: (hc: HecongApi) => void) => void
}
}
export {}完整的字段与类型见接口速查。