@syfei49/mini-dynamic-renderer 1.0.3 → 1.0.7
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 +54 -18
- package/components/ly-ai-assistant/ly-ai-assistant.vue +40 -6
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/chat/service.js +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @syfei49/mini-dynamic-renderer
|
|
2
2
|
|
|
3
|
-
uni-app 浮层 AI
|
|
3
|
+
uni-app 浮层 AI 客服插件。在指定页面右下角显示 AI 浮层图标,点击后从底部弹出抽屉聊天面板,支持文字、图片、拍照/相册上传及 `[NEED_ICON]` 分类图标引导。
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
@@ -9,54 +9,80 @@ cd your-uniapp-project
|
|
|
9
9
|
npm install @syfei49/mini-dynamic-renderer
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
本地调试可指向 tar 包:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@syfei49/mini-dynamic-renderer": "^1.0.7"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
12
22
|
微信开发者工具 → **工具** → **构建 npm**
|
|
13
23
|
|
|
14
24
|
## 接入
|
|
15
25
|
|
|
16
26
|
### 1. App.vue 初始化
|
|
17
27
|
|
|
28
|
+
只负责注入配置,不渲染 UI(微信小程序 `App.vue` 的 template 不会显示)。
|
|
29
|
+
|
|
18
30
|
```vue
|
|
19
31
|
<script setup>
|
|
20
32
|
import { init } from '@syfei49/mini-dynamic-renderer'
|
|
33
|
+
import { baseUrl } from '@/utils/request.js'
|
|
21
34
|
|
|
22
35
|
init({
|
|
23
|
-
baseUrl:
|
|
36
|
+
baseUrl: baseUrl,
|
|
24
37
|
chatApiPath: '/mini/ai/chat',
|
|
25
38
|
uploadApiPath: '/mini/common/upload',
|
|
26
39
|
getToken: () => uni.getStorageSync('token'),
|
|
27
40
|
visiblePages: ['pages/user/user'],
|
|
28
41
|
requireLogin: true,
|
|
29
|
-
loginPage: '/pages/login/login?noauto=1'
|
|
42
|
+
loginPage: '/pages/login/login?noauto=1',
|
|
43
|
+
debug: process.env.NODE_ENV === 'development'
|
|
30
44
|
})
|
|
31
45
|
</script>
|
|
32
46
|
```
|
|
33
47
|
|
|
34
|
-
### 2.
|
|
48
|
+
### 2. 目标页面引入组件
|
|
35
49
|
|
|
36
|
-
以 `pages/user/user.vue`
|
|
50
|
+
以 `pages/user/user.vue` 为例,模板末尾挂载一次:
|
|
37
51
|
|
|
38
52
|
```vue
|
|
39
|
-
<script setup>
|
|
40
|
-
import LyAiAssistant from '@syfei49/mini-dynamic-renderer/components/ly-ai-assistant/ly-ai-assistant.vue'
|
|
41
|
-
import { openChat } from '@syfei49/mini-dynamic-renderer'
|
|
42
|
-
</script>
|
|
43
|
-
|
|
44
53
|
<template>
|
|
45
|
-
<view>
|
|
46
|
-
<!--
|
|
54
|
+
<view class="container">
|
|
55
|
+
<!-- 页面原有内容 -->
|
|
56
|
+
<lyTabbar server="sc"></lyTabbar>
|
|
47
57
|
<LyAiAssistant />
|
|
48
58
|
</view>
|
|
49
59
|
</template>
|
|
60
|
+
|
|
61
|
+
<script setup>
|
|
62
|
+
import LyAiAssistant from '@syfei49/mini-dynamic-renderer/components/ly-ai-assistant/ly-ai-assistant.vue'
|
|
63
|
+
import { openChat } from '@syfei49/mini-dynamic-renderer'
|
|
64
|
+
</script>
|
|
50
65
|
```
|
|
51
66
|
|
|
52
|
-
|
|
67
|
+
### 3. 用 openChat 打开抽屉
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
把旧入口从 `uni.navigateTo('/pages/user/aiChat')` 改为直接打开抽屉:
|
|
55
70
|
|
|
56
71
|
```javascript
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
function gotoPath(item) {
|
|
73
|
+
if (item.title === '分销中心') {
|
|
74
|
+
handleDistributionCenter()
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (item.title === 'AI客服') {
|
|
79
|
+
openChat()
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 其他页面保持原来的 uni.navigateTo 逻辑
|
|
84
|
+
uni.navigateTo({ url: item.path })
|
|
85
|
+
}
|
|
60
86
|
```
|
|
61
87
|
|
|
62
88
|
## 配置项
|
|
@@ -87,8 +113,18 @@ openChat()
|
|
|
87
113
|
| `uninstall()` | 卸载拦截器,恢复原始 `wx.request` |
|
|
88
114
|
| `parseRenderConfig(data)` | 解析响应中的渲染配置(预留) |
|
|
89
115
|
|
|
116
|
+
## 后端协议
|
|
117
|
+
|
|
118
|
+
当需要用户上传分类图标时,后端可在 `answer` 中返回:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
[NEED_ICON]{"name": "张三", "sort": 0, "msg": "请上传一张图片作为「张三」的分类图标"}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
插件会弹出「拍照 / 从相册选择 / 取消」选择条,并在上传后自动把图标 URL 拼回对话继续请求。
|
|
125
|
+
|
|
90
126
|
## 版本说明
|
|
91
127
|
|
|
92
|
-
- **v1.0.
|
|
128
|
+
- **v1.0.x**:uni-app 浮层 AI 插件、抽屉聊天、迁移 aiChat 全能力、文档拆分
|
|
93
129
|
- v1.0.0:wx.request 拦截 + hello world 弹窗(测试版)
|
|
94
130
|
- 后续版本将根据后端 `renderConfig` 动态渲染更多组件类型
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<view v-else class="ly-ai-float-text">AI</view>
|
|
6
6
|
</view>
|
|
7
7
|
|
|
8
|
-
<view v-if="opened" class="ly-ai-overlay" @click
|
|
9
|
-
<view class="ly-ai-drawer">
|
|
8
|
+
<view v-if="opened" class="ly-ai-overlay" @click="onOverlayClick">
|
|
9
|
+
<view class="ly-ai-drawer" :style="drawerStyle" @click.stop>
|
|
10
10
|
<view class="ly-ai-header">
|
|
11
11
|
<text class="ly-ai-title">AI 智能客服</text>
|
|
12
12
|
<view class="ly-ai-close" @click="closeChat">×</view>
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<view class="ly-ai-input-bar">
|
|
56
56
|
<view class="ly-ai-attach-btn" :class="{ disabled: loading || uploading }" @click="chooseImage">+</view>
|
|
57
57
|
<input class="ly-ai-chat-input" :value="inputText" placeholder="请输入您的问题" confirm-type="send"
|
|
58
|
-
:disabled="loading || uploading" @input="onInput" @confirm="handleSend" />
|
|
58
|
+
:disabled="loading || uploading" :adjust-position="false" :hold-keyboard="true" @input="onInput" @confirm="handleSend" />
|
|
59
59
|
<view class="ly-ai-send-btn" :class="{ disabled: loading || uploading || !canSend }" @click="handleSend">发送</view>
|
|
60
60
|
</view>
|
|
61
61
|
</template>
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
const quickQuestions = ref([])
|
|
91
91
|
const scrollIntoView = ref('')
|
|
92
92
|
const floatIconUrl = ref('')
|
|
93
|
+
const keyboardHeight = ref(0)
|
|
93
94
|
|
|
94
95
|
const canSend = computed(() => {
|
|
95
96
|
return !!(inputText.value && inputText.value.trim()) || !!pendingImageUrl.value
|
|
@@ -102,9 +103,16 @@
|
|
|
102
103
|
}
|
|
103
104
|
})
|
|
104
105
|
|
|
106
|
+
const drawerStyle = computed(() => {
|
|
107
|
+
return {
|
|
108
|
+
bottom: keyboardHeight.value + 'px'
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
105
112
|
let updateHandler = null
|
|
106
113
|
let openHandler = null
|
|
107
114
|
let closeHandler = null
|
|
115
|
+
let keyboardHandler = null
|
|
108
116
|
|
|
109
117
|
function syncState(snapshot) {
|
|
110
118
|
messages.value = snapshot.messages || []
|
|
@@ -144,14 +152,23 @@
|
|
|
144
152
|
return
|
|
145
153
|
}
|
|
146
154
|
opened.value = true
|
|
155
|
+
keyboardHeight.value = 0
|
|
147
156
|
scrollToBottom()
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
function closeChat() {
|
|
151
160
|
opened.value = false
|
|
161
|
+
keyboardHeight.value = 0
|
|
152
162
|
chatService.closeConversation()
|
|
153
163
|
}
|
|
154
164
|
|
|
165
|
+
function onOverlayClick(e) {
|
|
166
|
+
if (e && e.target && e.currentTarget && e.target !== e.currentTarget) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
closeChat()
|
|
170
|
+
}
|
|
171
|
+
|
|
155
172
|
function onInput(e) {
|
|
156
173
|
inputText.value = e.detail.value
|
|
157
174
|
chatService.setInputText(e.detail.value)
|
|
@@ -214,14 +231,25 @@
|
|
|
214
231
|
}
|
|
215
232
|
openHandler = function () {
|
|
216
233
|
opened.value = true
|
|
234
|
+
keyboardHeight.value = 0
|
|
217
235
|
scrollToBottom()
|
|
218
236
|
}
|
|
219
237
|
closeHandler = function () {
|
|
220
238
|
opened.value = false
|
|
239
|
+
keyboardHeight.value = 0
|
|
240
|
+
}
|
|
241
|
+
keyboardHandler = function (res) {
|
|
242
|
+
keyboardHeight.value = res.height || 0
|
|
243
|
+
if (keyboardHeight.value > 0) {
|
|
244
|
+
scrollToBottom()
|
|
245
|
+
}
|
|
221
246
|
}
|
|
222
247
|
chatService.onUpdate(updateHandler)
|
|
223
248
|
chatService.onOpen(openHandler)
|
|
224
249
|
chatService.onClose(closeHandler)
|
|
250
|
+
if (typeof uni.onKeyboardHeightChange === 'function') {
|
|
251
|
+
uni.onKeyboardHeightChange(keyboardHandler)
|
|
252
|
+
}
|
|
225
253
|
syncState(chatService.getSnapshot())
|
|
226
254
|
checkVisible()
|
|
227
255
|
})
|
|
@@ -236,6 +264,9 @@
|
|
|
236
264
|
if (closeHandler) {
|
|
237
265
|
chatService.offClose(closeHandler)
|
|
238
266
|
}
|
|
267
|
+
if (keyboardHandler && typeof uni.offKeyboardHeightChange === 'function') {
|
|
268
|
+
uni.offKeyboardHeightChange(keyboardHandler)
|
|
269
|
+
}
|
|
239
270
|
})
|
|
240
271
|
|
|
241
272
|
onShow(() => {
|
|
@@ -276,18 +307,20 @@
|
|
|
276
307
|
bottom: 0;
|
|
277
308
|
z-index: 10000;
|
|
278
309
|
background: rgba(0, 0, 0, 0.4);
|
|
279
|
-
display: flex;
|
|
280
|
-
flex-direction: column;
|
|
281
|
-
justify-content: flex-end;
|
|
282
310
|
}
|
|
283
311
|
|
|
284
312
|
.ly-ai-drawer {
|
|
313
|
+
position: absolute;
|
|
314
|
+
left: 0;
|
|
315
|
+
right: 0;
|
|
316
|
+
bottom: 0;
|
|
285
317
|
height: 82vh;
|
|
286
318
|
background: #f5f6f8;
|
|
287
319
|
border-radius: 32rpx 32rpx 0 0;
|
|
288
320
|
display: flex;
|
|
289
321
|
flex-direction: column;
|
|
290
322
|
overflow: hidden;
|
|
323
|
+
transition: bottom 0.2s ease;
|
|
291
324
|
}
|
|
292
325
|
|
|
293
326
|
.ly-ai-header {
|
|
@@ -322,6 +355,7 @@
|
|
|
322
355
|
.ly-ai-list {
|
|
323
356
|
flex: 1;
|
|
324
357
|
padding: 24rpx;
|
|
358
|
+
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
325
359
|
box-sizing: border-box;
|
|
326
360
|
}
|
|
327
361
|
|
package/index.js
CHANGED
package/package.json
CHANGED
package/src/chat/service.js
CHANGED
|
@@ -223,7 +223,8 @@ function sendMessage(query, imageUrl, displayContent) {
|
|
|
223
223
|
query: query || undefined,
|
|
224
224
|
imageUrl: imageUrl || undefined
|
|
225
225
|
})
|
|
226
|
-
.then(function (
|
|
226
|
+
.then(function (res) {
|
|
227
|
+
var data = res && res.data ? res.data : res;
|
|
227
228
|
if (data.conversationId) {
|
|
228
229
|
state.conversationId = data.conversationId;
|
|
229
230
|
}
|