提交 | 用户 | age
|
36e1de
|
1 |
<template> |
L |
2 |
<div> |
|
3 |
<ul ref="chatList" class="chat-list" :style="`height:${height};`"> |
|
4 |
|
|
5 |
<li v-for="(item, index) in list" :key="index"> |
|
6 |
<!-- 时间 --> |
|
7 |
<div v-if="item.time_tx" class="flex"><div class="chat-time">{{ item.time_tx }}</div></div> |
|
8 |
|
|
9 |
<!-- 聊天主体 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ --> |
|
10 |
<!-- 通常主体 有用户信息 --> |
|
11 |
<div class="chat-item" :class="{'me-item': getDirection(item.fromUser)=='right'}"> |
|
12 |
<img :src="item.avatar" alt="" class="avatar" fit="cover"> |
|
13 |
<div class="info"> |
|
14 |
<!-- ({{ item.msgtype }}) --> |
|
15 |
<div class="name">{{ item.name }}</div> |
|
16 |
|
|
17 |
<!-- 消息内容 ↓↓↓↓↓↓↓↓↓↓ --> |
|
18 |
<!-- 文本 --> |
|
19 |
<textPop v-if="item.msgtype === 'text'" :data="item.content" :direction="getDirection(item.fromUser)" /> |
|
20 |
<!-- 图片 --> |
|
21 |
<imagePop v-if="item.msgtype === 'image'" :data="item" :direction="getDirection(item.fromUser)" /> |
|
22 |
<!-- 消息内容 ↑↑↑↑↑↑↑↑↑↑ --> |
|
23 |
</div> |
|
24 |
</div> |
|
25 |
<!-- 聊天主体 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ --> |
|
26 |
</li> |
|
27 |
|
|
28 |
<div v-if="loading" class="text-center"> |
|
29 |
<div class="ball-rotate"><div /></div> |
|
30 |
</div> |
|
31 |
</ul> |
|
32 |
|
|
33 |
<div class="handle-wrap flex flex-ver"> |
|
34 |
<input v-model="myTalk" type="text" class="input flex-1" maxlength="100" @keyup.enter="handleSend"> |
|
35 |
<div class="send-btn" @click="handleSend">发送</div> |
|
36 |
</div> |
|
37 |
|
|
38 |
<!-- audioFileUrl --> |
|
39 |
<audio ref="refAudio" src=""> |
|
40 |
您的浏览器不支持 audio 标签。 |
|
41 |
</audio> |
|
42 |
</div> |
|
43 |
</template> |
|
44 |
|
|
45 |
<script> |
|
46 |
// const listMockData = require('./chat_list_mockData') |
|
47 |
const chatFn = require('@/utils/chat/chat.js') |
|
48 |
import imagePop from './msg/imagePop.vue' |
|
49 |
import textPop from './msg/textPop.vue' |
|
50 |
import config from '../../config/index' |
|
51 |
export default { |
|
52 |
name: 'ChatList', |
|
53 |
components: { textPop, imagePop }, |
|
54 |
mixins: [], |
|
55 |
props: { |
|
56 |
// 高度 |
|
57 |
height: { |
|
58 |
type: String, |
|
59 |
default: '100vh' |
|
60 |
} |
|
61 |
}, |
|
62 |
data() { |
|
63 |
return { |
|
64 |
userId: '', |
|
65 |
myTalk: '', |
|
66 |
|
|
67 |
// 列表数据 |
|
68 |
loading: false, |
|
69 |
list: [] |
|
70 |
} |
|
71 |
}, |
|
72 |
mounted() { |
|
73 |
this.init() |
|
74 |
}, |
|
75 |
methods: { |
|
76 |
init() { |
|
77 |
this.userId = this.getQueryString('userId') |
|
78 |
console.log('this.userId', this.userId) |
|
79 |
if (!this.userId) window.appToast('缺少userID,请重新进入') |
|
80 |
}, |
|
81 |
|
|
82 |
// 判断位置 return 'left' 'right' |
|
83 |
getDirection(itemUserId) { |
|
84 |
return itemUserId === 'mySelf' ? 'right' : 'left' |
|
85 |
}, |
|
86 |
|
|
87 |
// 点击发送 |
|
88 |
handleSend() { |
|
89 |
if (this.loading) return window.appToast('操作过快,请稍后') |
|
90 |
if (!this.userId) return window.appToast('缺少userID,请重新进入') |
|
91 |
|
|
92 |
const myTalk = JSON.parse(JSON.stringify(this.myTalk)) |
|
93 |
const list = JSON.parse(JSON.stringify(this.list)) |
|
94 |
|
|
95 |
if (!myTalk) return window.appToast('请输入内容') |
|
96 |
this.myTalk = '' |
|
97 |
|
|
98 |
list.push({ |
|
99 |
content: { 'content': myTalk }, |
|
100 |
name: '我', |
|
101 |
msgtime: this.$moment().format('yyyy-MM-DD HH:mm:ss'), |
|
102 |
action: 'send', |
|
103 |
id: this.$moment().format('yyyyMMDDHHmmss'), |
|
104 |
avatar: require('@/assets/imgs/mySelf.png'), |
|
105 |
msgtype: 'text', |
|
106 |
fromUser: 'mySelf' |
|
107 |
}) |
|
108 |
|
|
109 |
// 处理聊天显示的日期 |
|
110 |
list && list.forEach((item, index) => { |
|
111 |
if (index !== 0) item.time_tx = chatFn.chatTime(item.msgtime, list[index - 1] && list[index - 1].msgtime) |
|
112 |
}) |
|
113 |
this.list = list |
|
114 |
|
|
115 |
this.$nextTick(() => { this.scrollBottom() }) |
|
116 |
|
|
117 |
this.getData(myTalk) |
|
118 |
}, |
|
119 |
|
|
120 |
// 获取ai返回内容 |
|
121 |
getData(keyWord) { |
|
122 |
console.log('getChatList') |
|
123 |
var { loading, userId } = this |
|
124 |
const list = JSON.parse(JSON.stringify(this.list)) |
|
125 |
if (loading) return |
|
126 |
|
|
127 |
this.loading = true |
|
128 |
|
|
129 |
const url = config.is_use_test_server ? 'crm-user/chatGpt/chat' : 'chat/msg' |
|
130 |
|
|
131 |
this.Req.http.post({ |
|
132 |
url: url, |
|
133 |
data: { |
|
134 |
msg: keyWord, |
|
135 |
userId: userId |
|
136 |
}, |
|
137 |
udData: { noLoading: true, nokey: true }, |
|
138 |
mockData: { |
|
139 |
code: 100, |
|
140 |
msg: '', |
|
141 |
data: { |
|
142 |
text: '机器人回复' |
|
143 |
} |
|
144 |
} |
|
145 |
}).then(res => { |
|
146 |
this.loading = false |
|
147 |
|
|
148 |
if (res.data) { |
|
149 |
const content = res.data.replace(/\n/g, '</br>') |
|
150 |
list.push({ |
|
151 |
content: { 'content': content }, |
|
152 |
name: 'AI', |
|
153 |
msgtime: this.$moment().format('yyyy-MM-DD HH:mm:ss'), |
|
154 |
action: 'send', |
|
155 |
id: this.$moment().format('yyyyMMDDHHmmss'), |
|
156 |
avatar: require('@/assets/imgs/AI.png'), |
|
157 |
msgtype: 'text', |
|
158 |
fromUser: 'robot' |
|
159 |
}) |
|
160 |
} |
|
161 |
|
|
162 |
// 处理聊天显示的日期 |
|
163 |
list && list.forEach((item, index) => { |
|
164 |
if (index !== 0) item.time_tx = chatFn.chatTime(item.msgtime, list[index - 1] && list[index - 1].msgtime) |
|
165 |
}) |
|
166 |
|
|
167 |
this.list = list |
|
168 |
|
|
169 |
this.$nextTick(() => { this.scrollBottom() }) |
|
170 |
}).catch(res => { |
|
171 |
this.loading = false |
|
172 |
const warnText = res.msg || ('请稍后再试...') |
|
173 |
window.appToast(warnText) |
|
174 |
}) |
|
175 |
}, |
|
176 |
|
|
177 |
scrollBottom() { |
|
178 |
this.$nextTick(() => { |
|
179 |
this.$refs.chatList.scrollTop = this.$refs.chatList.scrollHeight |
|
180 |
}) |
|
181 |
} |
|
182 |
} |
|
183 |
} |
|
184 |
</script> |
|
185 |
|
|
186 |
<style scoped> |
|
187 |
.loading-tx{ |
|
188 |
font-size: 22px; |
|
189 |
color: #999; |
|
190 |
} |
|
191 |
|
|
192 |
.chat-list{ |
|
193 |
overflow-y: auto; |
|
194 |
background-color: #fff; |
|
195 |
padding-top: 40px; |
|
196 |
box-sizing: border-box; |
|
197 |
padding-bottom: 140px; |
|
198 |
} |
|
199 |
|
|
200 |
.chat-time{ |
|
201 |
background-color: #DADADA; |
|
202 |
color: #fff; |
|
203 |
font-size: 24px; |
|
204 |
line-height: 1.2; |
|
205 |
padding: 4px 0; |
|
206 |
border-radius: 6px; |
|
207 |
text-align: center; |
|
208 |
margin: 6px auto 10px; |
|
209 |
padding: 4px 10px; |
|
210 |
} |
|
211 |
|
|
212 |
.chat-item{ |
|
213 |
margin-bottom: 20px; |
|
214 |
padding: 0 20px ; |
|
215 |
} |
|
216 |
.chat-item .avatar{ |
|
217 |
height: 60px; |
|
218 |
width: 60px; |
|
219 |
display: block; |
|
220 |
margin-right: 10px; |
|
221 |
border-radius: 4px; |
|
222 |
float: left; |
|
223 |
} |
|
224 |
.chat-item .info{ |
|
225 |
/* padding-top: 4px; */ |
|
226 |
display: inline-block; |
|
227 |
text-align: left; |
|
228 |
} |
|
229 |
.chat-item .info .name{ |
|
230 |
line-height: 1.2; |
|
231 |
height: 1.2em; |
|
232 |
margin-bottom: 4px; |
|
233 |
font-size: 22px; |
|
234 |
} |
|
235 |
.chat-item.me-item{ |
|
236 |
text-align: right; |
|
237 |
} |
|
238 |
.chat-item.me-item .avatar{ |
|
239 |
float: right; |
|
240 |
margin-right: 0; |
|
241 |
margin-left: 10px; |
|
242 |
} |
|
243 |
.chat-item.me-item .name{ |
|
244 |
text-align: right; |
|
245 |
} |
|
246 |
|
|
247 |
.handle-wrap{ |
|
248 |
background-color: #F7F7F7; |
|
249 |
position: fixed; |
|
250 |
bottom: 0; |
|
251 |
left: 0; |
|
252 |
width: 100%; |
|
253 |
padding: 20px 30px 60px 30px; |
|
254 |
box-sizing: border-box; |
|
255 |
} |
|
256 |
.handle-wrap .input{ |
|
257 |
height: 60px; |
|
258 |
line-height: 30px; |
|
259 |
font-size: 26px; |
|
260 |
border: none; |
|
261 |
background-color: #fff; |
|
262 |
border-radius: 6px; |
|
263 |
outline: none; |
|
264 |
} |
|
265 |
.handle-wrap .input:focus{ |
|
266 |
border: none; |
|
267 |
} |
|
268 |
.handle-wrap .send-btn{ |
|
269 |
background-color: #45B1D7; |
|
270 |
height: 60px; |
|
271 |
line-height: 60px; |
|
272 |
color: #fff; |
|
273 |
padding: 0 20px; |
|
274 |
border-radius: 6px; |
|
275 |
letter-spacing: 4px; |
|
276 |
margin-left: 20px; |
|
277 |
font-size: 28px; |
|
278 |
} |
|
279 |
|
|
280 |
@keyframes ball-rotate { |
|
281 |
0% {transform: rotate(0deg) scale(1); } |
|
282 |
|
|
283 |
50% {transform: rotate(180deg) scale(0.6);} |
|
284 |
|
|
285 |
100% {transform: rotate(360deg) scale(1);} |
|
286 |
} |
|
287 |
.ball-rotate{position: relative;width: 100px;height: 70px;margin: 0 auto 20px;} |
|
288 |
.ball-rotate > div { |
|
289 |
background-color: #000; |
|
290 |
width: 30px; |
|
291 |
height: 30px; |
|
292 |
border-radius: 100%; |
|
293 |
margin: 4px; |
|
294 |
animation-fill-mode: both; |
|
295 |
position: absolute; |
|
296 |
top: 50%; |
|
297 |
left: 50%; |
|
298 |
margin: -16px 0 0 -16px; |
|
299 |
} |
|
300 |
.ball-rotate > div:first-child { |
|
301 |
animation: ball-rotate 1s 0s cubic-bezier(.7, -.13, .22, .86) infinite; |
|
302 |
} |
|
303 |
.ball-rotate > div:before, .ball-rotate > div:after { |
|
304 |
background-color: #000; |
|
305 |
width: 30px; |
|
306 |
height: 30px; |
|
307 |
border-radius: 100%; |
|
308 |
margin: 4px; |
|
309 |
content: ""!important; |
|
310 |
position: absolute; |
|
311 |
opacity: .8; |
|
312 |
} |
|
313 |
.ball-rotate > div:before {top: 0px;left: -56px} |
|
314 |
.ball-rotate > div:after {top: 0px;left: 50px} |
|
315 |
</style> |