tgo-widget-miniprogram 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/miniprogram_dist/adapters/request.js +38 -0
- package/miniprogram_dist/adapters/storage.js +42 -0
- package/miniprogram_dist/adapters/systemInfo.js +22 -0
- package/miniprogram_dist/chat/index.js +164 -0
- package/miniprogram_dist/chat/index.json +7 -0
- package/miniprogram_dist/chat/index.wxml +48 -0
- package/miniprogram_dist/chat/index.wxss +92 -0
- package/miniprogram_dist/components/json-render-element/index.js +374 -0
- package/miniprogram_dist/components/json-render-element/index.json +6 -0
- package/miniprogram_dist/components/json-render-element/index.wxml +218 -0
- package/miniprogram_dist/components/json-render-element/index.wxss +450 -0
- package/miniprogram_dist/components/json-render-message/index.js +89 -0
- package/miniprogram_dist/components/json-render-message/index.json +7 -0
- package/miniprogram_dist/components/json-render-message/index.wxml +25 -0
- package/miniprogram_dist/components/json-render-message/index.wxss +26 -0
- package/miniprogram_dist/components/json-render-surface/index.js +116 -0
- package/miniprogram_dist/components/json-render-surface/index.json +6 -0
- package/miniprogram_dist/components/json-render-surface/index.wxml +10 -0
- package/miniprogram_dist/components/json-render-surface/index.wxss +6 -0
- package/miniprogram_dist/components/markdown-text/index.js +23 -0
- package/miniprogram_dist/components/markdown-text/index.json +3 -0
- package/miniprogram_dist/components/markdown-text/index.wxml +1 -0
- package/miniprogram_dist/components/markdown-text/index.wxss +6 -0
- package/miniprogram_dist/components/message-bubble/index.js +12 -0
- package/miniprogram_dist/components/message-bubble/index.json +3 -0
- package/miniprogram_dist/components/message-bubble/index.wxml +3 -0
- package/miniprogram_dist/components/message-bubble/index.wxss +17 -0
- package/miniprogram_dist/components/message-input/index.js +76 -0
- package/miniprogram_dist/components/message-input/index.json +3 -0
- package/miniprogram_dist/components/message-input/index.wxml +28 -0
- package/miniprogram_dist/components/message-input/index.wxss +56 -0
- package/miniprogram_dist/components/message-list/index.js +113 -0
- package/miniprogram_dist/components/message-list/index.json +9 -0
- package/miniprogram_dist/components/message-list/index.wxml +108 -0
- package/miniprogram_dist/components/message-list/index.wxss +113 -0
- package/miniprogram_dist/components/system-message/index.js +8 -0
- package/miniprogram_dist/components/system-message/index.json +3 -0
- package/miniprogram_dist/components/system-message/index.wxml +3 -0
- package/miniprogram_dist/components/system-message/index.wxss +15 -0
- package/miniprogram_dist/core/chatStore.js +758 -0
- package/miniprogram_dist/core/i18n.js +66 -0
- package/miniprogram_dist/core/platformStore.js +86 -0
- package/miniprogram_dist/core/types.js +192 -0
- package/miniprogram_dist/services/chat.js +67 -0
- package/miniprogram_dist/services/messageHistory.js +46 -0
- package/miniprogram_dist/services/platform.js +27 -0
- package/miniprogram_dist/services/upload.js +74 -0
- package/miniprogram_dist/services/visitor.js +67 -0
- package/miniprogram_dist/services/wukongim.js +183 -0
- package/miniprogram_dist/utils/jsonRender.js +158 -0
- package/miniprogram_dist/utils/markdown.js +31 -0
- package/miniprogram_dist/utils/time.js +85 -0
- package/miniprogram_dist/utils/uid.js +11 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# tgo-widget-miniprogram
|
|
2
|
+
|
|
3
|
+
TGO 访客聊天微信小程序组件。开发者 3 步完成接入。
|
|
4
|
+
|
|
5
|
+
## 快速接入
|
|
6
|
+
|
|
7
|
+
### 1. 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install tgo-widget-miniprogram
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
在微信开发者工具中点击 **工具 → 构建 npm**。
|
|
14
|
+
|
|
15
|
+
### 2. 页面配置
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
// pages/chat/chat.json
|
|
19
|
+
{
|
|
20
|
+
"usingComponents": {
|
|
21
|
+
"tgo-chat": "tgo-widget-miniprogram/chat/index"
|
|
22
|
+
},
|
|
23
|
+
"navigationBarTitleText": "在线客服"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 3. 使用组件
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- pages/chat/chat.wxml -->
|
|
31
|
+
<tgo-chat api-key="ak_live_xxx" api-base="https://your-api.com" />
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 跳转到客服页
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
wx.navigateTo({ url: '/pages/chat/chat' })
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 域名白名单
|
|
41
|
+
|
|
42
|
+
在小程序管理后台配置以下域名:
|
|
43
|
+
|
|
44
|
+
| 类型 | 域名 |
|
|
45
|
+
|------|------|
|
|
46
|
+
| request 合法域名 | API 服务域名 |
|
|
47
|
+
| socket 合法域名 | WuKongIM WSS 域名 |
|
|
48
|
+
| uploadFile 合法域名 | API 服务域名 |
|
|
49
|
+
| downloadFile 合法域名 | API 服务域名 |
|
|
50
|
+
|
|
51
|
+
## 组件属性
|
|
52
|
+
|
|
53
|
+
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
|
54
|
+
|------|------|------|--------|------|
|
|
55
|
+
| `api-key` | String | 是 | — | 平台 API Key |
|
|
56
|
+
| `api-base` | String | 是 | — | API 服务地址 |
|
|
57
|
+
| `theme-color` | String | 否 | `#2f80ed` | 主题色 |
|
|
58
|
+
| `lang` | String | 否 | `zh` | 语言(zh/en) |
|
|
59
|
+
| `visitor-name` | String | 否 | — | 访客名称 |
|
|
60
|
+
| `visitor-avatar` | String | 否 | — | 访客头像 URL |
|
|
61
|
+
| `custom-attrs` | Object | 否 | — | 自定义访客属性 |
|
|
62
|
+
|
|
63
|
+
## 功能支持
|
|
64
|
+
|
|
65
|
+
- 文本消息收发
|
|
66
|
+
- AI 流式回复(实时逐字显示)
|
|
67
|
+
- 图片消息发送与预览
|
|
68
|
+
- 历史消息加载(上拉加载更多)
|
|
69
|
+
- 系统消息展示(客服接入/转接/结束)
|
|
70
|
+
- Markdown 渲染(加粗、斜体、链接、列表、代码块)
|
|
71
|
+
- 多语言支持(中文/英文)
|
|
72
|
+
- IM 断线自动重连
|
|
73
|
+
|
|
74
|
+
## 开发
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# 构建 miniprogram_dist
|
|
78
|
+
npm run build
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
构建产物位于 `miniprogram_dist/`,微信开发者工具通过 `package.json` 的 `miniprogram` 字段读取此目录。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wx.request Promise adapter (replaces fetch)
|
|
3
|
+
*
|
|
4
|
+
* Returns: { ok, status, statusText, data, json(), text(), headers }
|
|
5
|
+
*/
|
|
6
|
+
function request({ url, method, headers, body, timeout }) {
|
|
7
|
+
return new Promise(function (resolve, reject) {
|
|
8
|
+
wx.request({
|
|
9
|
+
url: url,
|
|
10
|
+
method: (method || 'GET').toUpperCase(),
|
|
11
|
+
header: headers || {},
|
|
12
|
+
data: body,
|
|
13
|
+
timeout: timeout || 15000,
|
|
14
|
+
dataType: 'json',
|
|
15
|
+
success: function (res) {
|
|
16
|
+
var ok = res.statusCode >= 200 && res.statusCode < 300
|
|
17
|
+
resolve({
|
|
18
|
+
ok: ok,
|
|
19
|
+
status: res.statusCode,
|
|
20
|
+
statusText: ok ? 'OK' : 'Error',
|
|
21
|
+
data: res.data,
|
|
22
|
+
headers: res.header || {},
|
|
23
|
+
json: function () { return Promise.resolve(res.data) },
|
|
24
|
+
text: function () {
|
|
25
|
+
return Promise.resolve(
|
|
26
|
+
typeof res.data === 'string' ? res.data : JSON.stringify(res.data)
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
},
|
|
31
|
+
fail: function (err) {
|
|
32
|
+
reject(new Error('[request] ' + (err.errMsg || 'network error')))
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { request }
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wx storage adapter (replaces localStorage)
|
|
3
|
+
* Supports optional TTL expiry, same format as Web version.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function setJSON(key, value, ttlMs) {
|
|
7
|
+
var rec = { v: value }
|
|
8
|
+
if (ttlMs && ttlMs > 0) {
|
|
9
|
+
rec.e = Date.now() + ttlMs
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
wx.setStorageSync(key, JSON.stringify(rec))
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.warn('[storage] setJSON failed:', key, e)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getJSON(key) {
|
|
19
|
+
try {
|
|
20
|
+
var raw = wx.getStorageSync(key)
|
|
21
|
+
if (!raw) return null
|
|
22
|
+
var rec = JSON.parse(raw)
|
|
23
|
+
if (rec && typeof rec === 'object') {
|
|
24
|
+
if (rec.e && Date.now() > rec.e) {
|
|
25
|
+
wx.removeStorageSync(key)
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
return rec.v
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
try { wx.removeStorageSync(key) } catch (_) {}
|
|
32
|
+
}
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function remove(key) {
|
|
37
|
+
try {
|
|
38
|
+
wx.removeStorageSync(key)
|
|
39
|
+
} catch (e) {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { setJSON: setJSON, getJSON: getJSON, remove: remove }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wx system info adapter (replaces navigator.userAgent)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
function collectSystemInfo() {
|
|
6
|
+
try {
|
|
7
|
+
var info = wx.getSystemInfoSync()
|
|
8
|
+
return {
|
|
9
|
+
browser: 'WeChat MiniProgram',
|
|
10
|
+
operating_system: (info.platform || '') + ' ' + (info.system || ''),
|
|
11
|
+
source_detail: 'WeChat ' + (info.version || '') + ', SDK ' + (info.SDKVersion || '')
|
|
12
|
+
}
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return {
|
|
15
|
+
browser: 'WeChat MiniProgram',
|
|
16
|
+
operating_system: '',
|
|
17
|
+
source_detail: ''
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { collectSystemInfo: collectSystemInfo }
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <tgo-chat> main component
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* <tgo-chat api-key="ak_live_xxx" api-base="https://your-api.com" />
|
|
6
|
+
*/
|
|
7
|
+
var chatStore = require('../core/chatStore')
|
|
8
|
+
var platformStore = require('../core/platformStore')
|
|
9
|
+
var i18n = require('../core/i18n')
|
|
10
|
+
|
|
11
|
+
Component({
|
|
12
|
+
options: {
|
|
13
|
+
multipleSlots: true
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
properties: {
|
|
17
|
+
apiKey: {
|
|
18
|
+
type: String,
|
|
19
|
+
value: ''
|
|
20
|
+
},
|
|
21
|
+
apiBase: {
|
|
22
|
+
type: String,
|
|
23
|
+
value: ''
|
|
24
|
+
},
|
|
25
|
+
themeColor: {
|
|
26
|
+
type: String,
|
|
27
|
+
value: ''
|
|
28
|
+
},
|
|
29
|
+
lang: {
|
|
30
|
+
type: String,
|
|
31
|
+
value: 'zh'
|
|
32
|
+
},
|
|
33
|
+
visitorName: {
|
|
34
|
+
type: String,
|
|
35
|
+
value: ''
|
|
36
|
+
},
|
|
37
|
+
visitorAvatar: {
|
|
38
|
+
type: String,
|
|
39
|
+
value: ''
|
|
40
|
+
},
|
|
41
|
+
customAttrs: {
|
|
42
|
+
type: Object,
|
|
43
|
+
value: null
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
data: {
|
|
48
|
+
messages: [],
|
|
49
|
+
isStreaming: false,
|
|
50
|
+
streamCanceling: false,
|
|
51
|
+
online: false,
|
|
52
|
+
historyLoading: false,
|
|
53
|
+
historyHasMore: true,
|
|
54
|
+
initializing: false,
|
|
55
|
+
error: null,
|
|
56
|
+
resolvedThemeColor: '#2f80ed',
|
|
57
|
+
title: 'Tgo',
|
|
58
|
+
logoUrl: ''
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
lifetimes: {
|
|
62
|
+
attached: function () {
|
|
63
|
+
// Set i18n language
|
|
64
|
+
i18n.setLang(this.properties.lang)
|
|
65
|
+
|
|
66
|
+
// Subscribe to stores
|
|
67
|
+
this._bindStores()
|
|
68
|
+
|
|
69
|
+
// Initialize
|
|
70
|
+
this._init()
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
detached: function () {
|
|
74
|
+
if (this._unsubChat) { this._unsubChat(); this._unsubChat = null }
|
|
75
|
+
if (this._unsubPlatform) { this._unsubPlatform(); this._unsubPlatform = null }
|
|
76
|
+
chatStore.disconnect()
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
methods: {
|
|
81
|
+
_bindStores: function () {
|
|
82
|
+
var self = this
|
|
83
|
+
|
|
84
|
+
// Subscribe to chat store
|
|
85
|
+
this._unsubChat = chatStore.subscribe(function (state) {
|
|
86
|
+
self.setData({
|
|
87
|
+
messages: state.messages,
|
|
88
|
+
isStreaming: state.isStreaming,
|
|
89
|
+
streamCanceling: state.streamCanceling,
|
|
90
|
+
online: state.online,
|
|
91
|
+
historyLoading: state.historyLoading,
|
|
92
|
+
historyHasMore: state.historyHasMore,
|
|
93
|
+
initializing: state.initializing,
|
|
94
|
+
error: state.error
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Subscribe to platform store
|
|
99
|
+
this._unsubPlatform = platformStore.subscribe(function (state) {
|
|
100
|
+
var cfg = state.config || {}
|
|
101
|
+
self.setData({
|
|
102
|
+
title: cfg.widget_title || 'Tgo',
|
|
103
|
+
logoUrl: cfg.logo_url || '',
|
|
104
|
+
resolvedThemeColor: self.properties.themeColor || cfg.theme_color || '#2f80ed'
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Inject welcome message
|
|
108
|
+
if (cfg.welcome_message && !state.welcomeInjected) {
|
|
109
|
+
chatStore.ensureWelcomeMessage(cfg.welcome_message)
|
|
110
|
+
platformStore.markWelcomeInjected()
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
_init: function () {
|
|
116
|
+
var apiBase = this.properties.apiBase
|
|
117
|
+
var apiKey = this.properties.apiKey
|
|
118
|
+
|
|
119
|
+
if (!apiBase || !apiKey) {
|
|
120
|
+
console.error('[tgo-chat] api-base and api-key are required')
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Init platform config
|
|
125
|
+
platformStore.init(apiBase, apiKey)
|
|
126
|
+
|
|
127
|
+
// Set resolved theme color
|
|
128
|
+
this.setData({
|
|
129
|
+
resolvedThemeColor: this.properties.themeColor || '#2f80ed'
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// Init chat IM
|
|
133
|
+
chatStore.initIM({
|
|
134
|
+
apiBase: apiBase,
|
|
135
|
+
platformApiKey: apiKey
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// Event handlers from child components
|
|
140
|
+
onSendMessage: function (e) {
|
|
141
|
+
var text = e.detail && e.detail.text
|
|
142
|
+
if (text) {
|
|
143
|
+
chatStore.sendMessage(text)
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
onCancelStream: function () {
|
|
148
|
+
chatStore.cancelStreaming('user_cancel')
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
onLoadMore: function () {
|
|
152
|
+
if (!chatStore.getState().historyLoading && chatStore.getState().historyHasMore) {
|
|
153
|
+
chatStore.loadMoreHistory(20)
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
onChooseImage: function (e) {
|
|
158
|
+
var tempFilePath = e.detail && e.detail.tempFilePath
|
|
159
|
+
if (tempFilePath) {
|
|
160
|
+
chatStore.uploadImage(tempFilePath)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<view class="tgo-chat">
|
|
2
|
+
<!-- Header -->
|
|
3
|
+
<view class="chat-header" style="background-color: {{resolvedThemeColor}};">
|
|
4
|
+
<image wx:if="{{logoUrl}}" class="header-logo" src="{{logoUrl}}" mode="aspectFit" />
|
|
5
|
+
<text class="header-title">{{title}}</text>
|
|
6
|
+
<view wx:if="{{online}}" class="status-dot online"></view>
|
|
7
|
+
<view wx:else class="status-dot offline"></view>
|
|
8
|
+
</view>
|
|
9
|
+
|
|
10
|
+
<!-- Initializing state -->
|
|
11
|
+
<view wx:if="{{initializing}}" class="init-loading">
|
|
12
|
+
<text class="init-text">连接中...</text>
|
|
13
|
+
</view>
|
|
14
|
+
|
|
15
|
+
<!-- Error state -->
|
|
16
|
+
<view wx:elif="{{error && !messages.length}}" class="init-error">
|
|
17
|
+
<text class="error-text">{{error}}</text>
|
|
18
|
+
<view class="retry-btn" bindtap="_init">
|
|
19
|
+
<text class="retry-text">重试</text>
|
|
20
|
+
</view>
|
|
21
|
+
</view>
|
|
22
|
+
|
|
23
|
+
<!-- Chat body -->
|
|
24
|
+
<view wx:else class="chat-body">
|
|
25
|
+
<message-list
|
|
26
|
+
messages="{{messages}}"
|
|
27
|
+
historyLoading="{{historyLoading}}"
|
|
28
|
+
historyHasMore="{{historyHasMore}}"
|
|
29
|
+
isStreaming="{{isStreaming}}"
|
|
30
|
+
themeColor="{{resolvedThemeColor}}"
|
|
31
|
+
bind:loadmore="onLoadMore"
|
|
32
|
+
bind:sendmessage="onSendMessage"
|
|
33
|
+
/>
|
|
34
|
+
</view>
|
|
35
|
+
|
|
36
|
+
<!-- Input bar -->
|
|
37
|
+
<view class="chat-footer">
|
|
38
|
+
<message-input
|
|
39
|
+
isStreaming="{{isStreaming}}"
|
|
40
|
+
streamCanceling="{{streamCanceling}}"
|
|
41
|
+
themeColor="{{resolvedThemeColor}}"
|
|
42
|
+
disabled="{{initializing}}"
|
|
43
|
+
bind:send="onSendMessage"
|
|
44
|
+
bind:cancel="onCancelStream"
|
|
45
|
+
bind:image="onChooseImage"
|
|
46
|
+
/>
|
|
47
|
+
</view>
|
|
48
|
+
</view>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
.tgo-chat {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
height: 100vh;
|
|
5
|
+
background: #fff;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Header */
|
|
9
|
+
.chat-header {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
padding: 0 24rpx;
|
|
13
|
+
height: 88rpx;
|
|
14
|
+
flex-shrink: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.header-logo {
|
|
18
|
+
width: 48rpx;
|
|
19
|
+
height: 48rpx;
|
|
20
|
+
border-radius: 8rpx;
|
|
21
|
+
margin-right: 12rpx;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.header-title {
|
|
25
|
+
font-size: 32rpx;
|
|
26
|
+
color: #fff;
|
|
27
|
+
font-weight: 600;
|
|
28
|
+
flex: 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.status-dot {
|
|
32
|
+
width: 16rpx;
|
|
33
|
+
height: 16rpx;
|
|
34
|
+
border-radius: 50%;
|
|
35
|
+
margin-left: 12rpx;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.status-dot.online {
|
|
39
|
+
background: #4cd964;
|
|
40
|
+
box-shadow: 0 0 4rpx rgba(76, 217, 100, 0.6);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.status-dot.offline {
|
|
44
|
+
background: #ccc;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Loading / Error states */
|
|
48
|
+
.init-loading,
|
|
49
|
+
.init-error {
|
|
50
|
+
flex: 1;
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.init-text {
|
|
58
|
+
font-size: 28rpx;
|
|
59
|
+
color: #999;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.error-text {
|
|
63
|
+
font-size: 26rpx;
|
|
64
|
+
color: #e74c3c;
|
|
65
|
+
text-align: center;
|
|
66
|
+
padding: 0 40rpx;
|
|
67
|
+
margin-bottom: 24rpx;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.retry-btn {
|
|
71
|
+
padding: 12rpx 48rpx;
|
|
72
|
+
background: #f0f0f0;
|
|
73
|
+
border-radius: 32rpx;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.retry-text {
|
|
77
|
+
font-size: 28rpx;
|
|
78
|
+
color: #333;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Chat body */
|
|
82
|
+
.chat-body {
|
|
83
|
+
flex: 1;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Footer */
|
|
88
|
+
.chat-footer {
|
|
89
|
+
flex-shrink: 0;
|
|
90
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
91
|
+
background: #fff;
|
|
92
|
+
}
|